/*
Try the simple topo:
        h0 ---s0 --- s1 --- h1
               |      |
               s2 ----
*/

#include <ns3/core-module.h>
#include <ns3/network-module.h>
#include <ns3/csma-module.h>
#include <ns3/internet-module.h>
#include <ns3/ofswitch13-module.h>
#include <ns3/internet-apps-module.h>

#include "ns3/applications-module.h"

using namespace ns3;

class Controller;


class MyApp : public Application
{
public:
  MyApp ();
  virtual ~MyApp ();

  /**
   * Register this type.
   * \return The TypeId.
   */
  static TypeId GetTypeId (void);
  void Setup (Ptr<Socket> socket, Address address, uint32_t packetSize, uint32_t nPackets, DataRate dataRate, uint8_t myTos);

private:
  virtual void StartApplication (void);
  virtual void StopApplication (void);

  void ScheduleTx (void);
  void SendPacket (void);

  Ptr<Socket>     m_socket;
  Address         m_peer;
  uint32_t        m_packetSize;
  uint32_t        m_nPackets;
  DataRate        m_dataRate;
  EventId         m_sendEvent;
  bool            m_running;
  uint32_t        m_packetsSent;
  uint8_t         m_myTos;
};

MyApp::MyApp ()
  : m_socket (0),
    m_peer (),
    m_packetSize (0),
    m_nPackets (0),
    m_dataRate (0),
    m_sendEvent (),
    m_running (false),
    m_packetsSent (0),
    m_myTos (0x00)
{
}

MyApp::~MyApp ()
{
  m_socket = 0;
}

/* static */
TypeId MyApp::GetTypeId (void)
{
  static TypeId tid = TypeId ("MyApp")
    .SetParent<Application> ()
    .SetGroupName ("Tutorial")
    .AddConstructor<MyApp> ()
    ;
  return tid;
}

void
MyApp::Setup (Ptr<Socket> socket, Address address, uint32_t packetSize, uint32_t nPackets, DataRate dataRate, uint8_t myTos)
{
  m_socket = socket;
  m_peer = address;
  m_packetSize = packetSize;
  m_nPackets = nPackets;
  m_dataRate = dataRate;
  m_myTos = myTos;
}

void
MyApp::StartApplication (void)
{
  m_running = true;
  m_packetsSent = 0;

  m_socket->Bind ();
  m_socket->Connect (m_peer);
  m_socket->SetIpTos(m_myTos); // 添加tos，提供给ofswitch13
  SendPacket ();
}

void
MyApp::StopApplication (void)
{
  m_running = false;

  if (m_sendEvent.IsRunning ())
    {
      Simulator::Cancel (m_sendEvent);
    }

  if (m_socket)
    {
      m_socket->Close ();
    }
}

void
MyApp::SendPacket (void)
{
  Ptr<Packet> packet = Create<Packet> (m_packetSize);
  //test
  // m_socket->SetIpTos(0x08);
  m_socket->Send (packet);

  if (++m_packetsSent < m_nPackets)
    {
      ScheduleTx ();
    }
}

void
MyApp::ScheduleTx (void)
{
  if (m_running)
    {
      Time tNext (Seconds (m_packetSize * 8 / static_cast<double> (m_dataRate.GetBitRate ())));
      m_sendEvent = Simulator::Schedule (tNext, &MyApp::SendPacket, this);
    }
}


int
main (int argc, char *argv[])
{
  uint16_t simTime = 10;
  bool verbose = false;
  bool trace = false;

  // Configure command line parameters
  CommandLine cmd;
  cmd.AddValue ("simTime", "Simulation time (seconds)", simTime);
  cmd.AddValue ("verbose", "Enable verbose output", verbose);
  cmd.AddValue ("trace", "Enable datapath stats and pcap traces", trace);
  cmd.Parse (argc, argv);

  Config::SetDefault ("ns3::TcpSocket::SegmentSize", UintegerValue(1500));


  LogComponentEnable ("UdpEchoClientApplication", LOG_LEVEL_INFO);
  LogComponentEnable ("UdpEchoServerApplication", LOG_LEVEL_INFO);
  LogComponentEnable ("OnOffApplication", LOG_LEVEL_ALL);
  LogComponentEnable ("PacketSink", LOG_LEVEL_ALL);

  if (verbose)
    {
      OFSwitch13Helper::EnableDatapathLogs ();
      LogComponentEnable ("OFSwitch13Interface", LOG_LEVEL_ALL);
      LogComponentEnable ("OFSwitch13Device", LOG_LEVEL_ALL);
      LogComponentEnable ("OFSwitch13Port", LOG_LEVEL_ALL);
      LogComponentEnable ("OFSwitch13Queue", LOG_LEVEL_ALL);
      LogComponentEnable ("OFSwitch13SocketHandler", LOG_LEVEL_ALL);
      LogComponentEnable ("OFSwitch13Controller", LOG_LEVEL_ALL);
      LogComponentEnable ("OFSwitch13LearningController", LOG_LEVEL_ALL);
      LogComponentEnable ("OFSwitch13Helper", LOG_LEVEL_ALL);
      LogComponentEnable ("OFSwitch13InternalHelper", LOG_LEVEL_ALL);
    }

  // Enable checksum computations (required by OFSwitch13 module)
  GlobalValue::Bind ("ChecksumEnabled", BooleanValue (true));

  // Create two host nodes
  NodeContainer hosts;
  hosts.Create (2);

  // Create two switch nodes
  NodeContainer switches;
  switches.Create (3);

  // Use the CsmaHelper to connect hosts and switches
  CsmaHelper csmaHelper;
  csmaHelper.SetChannelAttribute ("DataRate", DataRateValue (DataRate ("1Mbps")));
  // csmaHelper.SetChannelAttribute ("DataRate", DataRateValue (DataRate ("1Gbps")));
  csmaHelper.SetChannelAttribute ("Delay", TimeValue (MilliSeconds (0.002)));

  NodeContainer pair;
  NetDeviceContainer pairDevs;
  NetDeviceContainer hostDevices;
  NetDeviceContainer switchPorts [3];

  // Connect host 0 to first switch
  pair = NodeContainer (hosts.Get (0), switches.Get (0));
  pairDevs = csmaHelper.Install (pair);
  hostDevices.Add (pairDevs.Get (0));
  switchPorts [0].Add (pairDevs.Get (1));  // 这里顺序会影响到 ofswitch controller 控制的switch port号

  // Connect host 1 to second switch
  pair = NodeContainer (hosts.Get (1), switches.Get (1));
  pairDevs = csmaHelper.Install (pair);
  hostDevices.Add (pairDevs.Get (0));
  switchPorts [1].Add (pairDevs.Get (1));  // 这里顺序会影响到 ofswitch controller 控制的switch port号

  // Connect the switches 0 and 1
  pair = NodeContainer (switches.Get (0), switches.Get (1));
  pairDevs = csmaHelper.Install (pair);
  switchPorts [0].Add (pairDevs.Get (0));  // 这里顺序会影响到 ofswitch controller 控制的switch port号
  switchPorts [1].Add (pairDevs.Get (1));  // 这里顺序会影响到 ofswitch controller 控制的switch port号

  // connect the switches 0 and 2 and 1
  pairDevs = csmaHelper.Install(NodeContainer (switches.Get (0), switches.Get (2)));
  switchPorts[0].Add(pairDevs.Get(0));  // 这里顺序会影响到 ofswitch controller 控制的switch port号
  switchPorts[2].Add(pairDevs.Get(1));  // 这里顺序会影响到 ofswitch controller 控制的switch port号
  
  pairDevs = csmaHelper.Install(NodeContainer (switches.Get (2), switches.Get (1)));
  switchPorts[2].Add(pairDevs.Get(0));  // 这里顺序会影响到 ofswitch controller 控制的switch port号
  switchPorts[1].Add(pairDevs.Get(1));  // 这里顺序会影响到 ofswitch controller 控制的switch port号

  // Create one controller nodes
  Ptr<Node> controllerNode = CreateObject<Node> ();
  Ptr<Controller> ctrl = CreateObject<Controller> (); 

  // 绑定控制器类，绑定交换机与控制器
  Ptr<OFSwitch13InternalHelper> of13Helper = CreateObject<OFSwitch13InternalHelper> ();
  of13Helper->InstallController (controllerNode, ctrl);
  of13Helper->InstallSwitch (switches.Get (0), switchPorts [0]);
  of13Helper->InstallSwitch (switches.Get (1), switchPorts [1]);
  of13Helper->InstallSwitch (switches.Get (2), switchPorts [2]);
  of13Helper->CreateOpenFlowChannels ();

  // Install the TCP/IP stack into hosts nodes
  InternetStackHelper inetStack;
  inetStack.Install (hosts);

  // Set IPv4 host addresses
  Ipv4AddressHelper ipv4helpr;
  Ipv4InterfaceContainer hostIpIfaces;
  ipv4helpr.SetBase ("10.1.1.0", "255.255.255.0");
  hostIpIfaces = ipv4helpr.Assign (hostDevices);  // hostDevices已经含多个元素了
  
  // 添加tos的打流
  // tcp server
  ApplicationContainer sinkApplications;
  InetSocketAddress serverSocket (Ipv4Address::GetAny (), 8000);
  PacketSinkHelper packetSinkHelper ("ns3::TcpSocketFactory", serverSocket);
  sinkApplications.Add (packetSinkHelper.Install (hosts.Get (1)));
  sinkApplications.Start (Seconds (0.0));
  sinkApplications.Stop (Seconds (10));

  // tcp client
  InetSocketAddress sinkAddress (hostIpIfaces.GetAddress(1), 8000);
  Ptr<Socket> ns3TcpSocket = Socket::CreateSocket (hosts.Get (0), TcpSocketFactory::GetTypeId ());
  Ptr<MyApp> clientApp = CreateObject<MyApp> ();
  clientApp->Setup (ns3TcpSocket, sinkAddress, 1040, 1000, DataRate ("1Mbps"), 0x08);
  hosts.Get (0)->AddApplication (clientApp);
  clientApp->SetStartTime (Seconds (1.));
  clientApp->SetStopTime (Seconds (10.));
  ////////

  // Enable datapath stats and pcap traces at hosts, switch(es), and controller(s)
  if (true)
  // if (trace)
    {
      of13Helper->EnableOpenFlowPcap ("openflow-hehe");
      of13Helper->EnableDatapathStats ("switch-stats");
      csmaHelper.EnablePcapAll("csma");
      // csmaHelper.EnablePcap ("switch", switchPorts [0], true);
      // csmaHelper.EnablePcap ("switch", switchPorts [1], true);
      // csmaHelper.EnablePcap ("switch", switchPorts [2], true);
      // csmaHelper.EnablePcap ("host", hostDevices);
    }

  std::ofstream ascii;
  Ptr<OutputStreamWrapper> ascii_wrap;
  ascii.open ("mytest-threshold-ascii");
  ascii_wrap = new OutputStreamWrapper ("mytest-threshold-ascii",
                                        std::ios::out);
  inetStack.EnableAsciiIpv4All (ascii_wrap);

  // Run the simulation
  Simulator::Stop (Seconds (simTime));
  Simulator::Run ();
  Simulator::Destroy ();

  uint64_t totalPacketsThrough = DynamicCast<PacketSink> (sinkApplications.Get (0))->GetTotalRx ();
  std::cout << "debug totalPacketsThrough: " << totalPacketsThrough << std::endl;
}

class Controller : public OFSwitch13Controller
{
protected:
  // Inherited from OFSwitch13Controller
  void HandshakeSuccessful (Ptr<const RemoteSwitch> swtch);
};

void
Controller::HandshakeSuccessful(Ptr<const RemoteSwitch> swtch)
{
  // 直连一跳方案
  // if (swtch->GetDpId() == 1) {
  //   DpctlExecute (swtch, "flow-mod cmd=add,table=0,prio=1 in_port=1 apply:output=2");
  //   DpctlExecute (swtch, "flow-mod cmd=add,table=0,prio=1 in_port=2 apply:output=1");
  // }
  // if (swtch->GetDpId() == 2) {
  //   DpctlExecute (swtch, "flow-mod cmd=add,table=0,prio=1 in_port=1 apply:output=2");
  //   DpctlExecute (swtch, "flow-mod cmd=add,table=0,prio=1 in_port=2 apply:output=1");
  // }
  ////

  // 直连两跳方案
  // if (swtch->GetDpId() == 1) {
  //   DpctlExecute (swtch, "flow-mod cmd=add,table=0,prio=1 in_port=1 apply:output=3");
  //   DpctlExecute (swtch, "flow-mod cmd=add,table=0,prio=1 in_port=3 apply:output=1");
  // }
  // if (swtch->GetDpId() == 2) {
  //   DpctlExecute (swtch, "flow-mod cmd=add,table=0,prio=1 in_port=1 apply:output=3");
  //   DpctlExecute (swtch, "flow-mod cmd=add,table=0,prio=1 in_port=3 apply:output=1");
  // }
  // if (swtch->GetDpId() == 3) {
  //   DpctlExecute (swtch, "flow-mod cmd=add,table=0,prio=1 in_port=1 apply:output=2");
  //   DpctlExecute (swtch, "flow-mod cmd=add,table=0,prio=1 in_port=2 apply:output=1");
  // }
  ////

  // h0->h1 方向限流分流
  if (swtch->GetDpId() == 1) {
    DpctlExecute (swtch, "meter-mod cmd=add,meter=1 dscp_remark:rate=8000,prec_level=1");
    // 从 s0 的 1 口进入的流量，去meter表分流
    DpctlExecute (swtch, "flow-mod cmd=add,table=0 in_port=1 meter:1 goto:1");
    // 未来这里可以继续进入group表按比例分流，此处直接分流出口流出
    DpctlExecute (swtch, "flow-mod cmd=add,table=1,prio=1 in_port=1,eth_type=0x806 apply:output=2");
    DpctlExecute (swtch, "flow-mod cmd=add,table=1 in_port=1,eth_type=0x0800,ip_dscp=2 apply:output=2");
    DpctlExecute (swtch, "flow-mod cmd=add,table=1 in_port=1,eth_type=0x0800,ip_dscp=4 apply:output=3");
    // 回来的流量直接流入
    DpctlExecute (swtch, "flow-mod cmd=add,table=0 in_port=3, apply:output=1");
    DpctlExecute (swtch, "flow-mod cmd=add,table=0 in_port=2, apply:output=1");
  }
  if (swtch->GetDpId() == 2) {
    // 从h2出去的流量直连过去
    DpctlExecute (swtch, "flow-mod cmd=add,table=0,prio=1 in_port=1 apply:output=2");
    // 让直连两跳都能到达 h2
    DpctlExecute (swtch, "flow-mod cmd=add,table=0,prio=1 in_port=3 apply:output=1");
    DpctlExecute (swtch, "flow-mod cmd=add,table=0,prio=1 in_port=2 apply:output=1");
  }
  if (swtch->GetDpId() == 3) {
    DpctlExecute (swtch, "flow-mod cmd=add,table=0,prio=1 in_port=1 apply:output=2");
    DpctlExecute (swtch, "flow-mod cmd=add,table=0,prio=1 in_port=2 apply:output=1");
  }
  ////

  // weight 分流
  // if (swtch->GetDpId() == 1) {
  //   // 进group表分流
  //   DpctlExecute (swtch, "group-mod cmd=add,type=sel,group=1 "
  //                   "weight=1,port=any,group=any output=2 "
  //                   "weight=1,port=any,group=any output=3");
  //   DpctlExecute (swtch, "flow-mod cmd=add,table=0,prio=700 "
  //             "in_port=1 apply:group=1");
  //   // 回来的路
  //   DpctlExecute (swtch, "flow-mod cmd=add,table=0,prio=1 in_port=3 apply:output=1");
  //   DpctlExecute (swtch, "flow-mod cmd=add,table=0,prio=1 in_port=2 apply:output=1");
  // }
  // if (swtch->GetDpId() == 2) {
  //   // 从h2出去的流量直连过去
  //   DpctlExecute (swtch, "flow-mod cmd=add,table=0,prio=1 in_port=1 apply:output=2");
  //   // 让直连两跳都能到达 h2
  //   DpctlExecute (swtch, "flow-mod cmd=add,table=0,prio=1 in_port=3 apply:output=1");
  //   DpctlExecute (swtch, "flow-mod cmd=add,table=0,prio=1 in_port=2 apply:output=1");
  // }
  // if (swtch->GetDpId() == 3) {
  //   DpctlExecute (swtch, "flow-mod cmd=add,table=0,prio=1 in_port=1 apply:output=2");
  //   DpctlExecute (swtch, "flow-mod cmd=add,table=0,prio=1 in_port=2 apply:output=1");
  // }  
  ////
  
  NS_LOG_UNCOND ("Debug NS_LOG_UNCOND");
  NS_LOG_UNCOND (DpctlExecute (swtch, "stats-flow"));  // 没用，统计用 of13Helper->EnableDatapathStats ("switch-stats");
}